#include "file_list.h"
#include "num/crc32.h"

#include "script/scripthelper.h"
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    
#include <stdlib.h>

#ifdef _ENABLE_LUABIND
#include <luabind/luabind.hpp>
#endif //_ENABLE_LUABIND

#ifdef __APPLE__
#include <sys/types.h>
#include <dirent.h>
#endif

#ifdef _PLATFORM_WIN32
	#define _USEWIN32API_
#else  //_PLATFORM_SDL
	#ifdef WIN32
		#include<windows.h>
		#define _USEWIN32API_
	#endif
#endif

#include <algorithm>

#include <cassert>
using namespace script;

class FileListContainer
{
public:
	FileListContainer()
	{

	};
	~FileListContainer()
	{
		for (unsigned int i=0; i<m_pFileLists.size(); i++)
		{
			delete(m_pFileLists[i]);
		}
	}
	std::vector<FileList*>			m_pFileLists;
};

static FileListContainer FC;

FileList::FileList()
{

}

FileList::~FileList()
{
}

void						FileList::Reset()
{
	m_Files.resize(0);
}


int							FileList::GetFileC()
{
	return m_Files.size();
}

std::string					FileList::GetFileName(int Index)
{
	FileInfo*	pInfo = GetFileInfo(Index);
	if (pInfo) return pInfo->Name;
	return "";
}

std::string					FileList::GetBaseFileName(int Index)
{
	FileInfo*	pInfo = GetFileInfo(Index);
	if (pInfo)
	{
		int				From = pInfo->Name.find_last_of("/\\");
		int				To   = pInfo->Name.find_last_of(".");
		
		if (From < 0) From = 0; else From = From +1;
		if (To < 0) To = pInfo->Name.size();
		std::string		Base= pInfo->Name.substr(From, To-From);
		return Base;
	}
	return "";
}

std::string					FileList::GetExtension(int Index)
{
	FileInfo*	pInfo = GetFileInfo(Index);
	if (pInfo) 
	{
		int	From	= pInfo->Name.find_last_of(".");
		int	To		= pInfo->Name.size();
		if (From < 0) From = 0; else From = From +1;
		std::string		Base= pInfo->Name.substr(From, To-From);
		return Base;
	}
	return "";
}


std::string					FileList::GetFileDate(int Index)
{
	FileInfo*	pInfo = GetFileInfo(Index);
	if (pInfo) return pInfo->Date;
	return "";
}

bool						FileList::CheckFlag(int Index, unsigned long Flag)
{
	FileInfo*	pInfo = GetFileInfo(Index);
	if (pInfo) return (pInfo->Flags & Flag) != 0;
	return false;
}

FileList::FileInfo*					FileList::GetFileInfo(int Index)
{
	if (Index < (int)m_Files.size() && Index >= 0) return &m_Files[Index];
	return NULL;
}

FileList*			FileList::SearchDir(const std::string& Path)
{
	FileList*	pList = new FileList;
	pList->Build(Path);
	FC.m_pFileLists.push_back(pList);
	return pList;
}

bool FileList::SortByNumber( const FileInfo& A,  const FileInfo& B )
{
	int	SA = A.Name.find_first_of("0123456789");
	int	SB = B.Name.find_first_of("0123456789");
	if (SA >= 0 && SB < 0) return true;		// don't swap, only A is valid
	if (SB >= 0 && SA < 0) return false;	// swap... only B is valid
	if (SA < 0 && SB < 0) return true;	//no need to swap

	// both are numbers.. let's find the numbers
	std::string	AStr = A.Name.substr(SA, A.Name.size()-SA);
	std::string	BStr = B.Name.substr(SB, B.Name.size()-SB);

	int		IndexA = atoi(AStr.c_str());
	int		IndexB = atoi(BStr.c_str());

	return IndexA < IndexB;

}

void				FileList::SortNumber()
{
	std::sort(m_Files.begin(), m_Files.end(), SortByNumber);
}

bool	FileList::CheckFile(const std::string& File, FileFullData& Data, bool GetCRC)
{
#ifdef _USEWIN32API_
	WIN32_FIND_DATA	FileData;
	HANDLE	hFile = FindFirstFile(File.c_str(), & FileData);
	if (hFile ==  INVALID_HANDLE_VALUE)		// invalid file
	{
		Data.Exists = false;
		return false;
	}

	Data.Name = FileData.cFileName;
	Data.Exists = true;
	Data.FileSize = FileData.nFileSizeLow;
	SYSTEMTIME	LastTouched;
	FileTimeToSystemTime(&FileData.ftLastWriteTime, &LastTouched);
	// convert?
	Data.LastTouched = 0;	
	Data.Flags = 0;
	if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)	Data.Flags |= FF_DIRECTORY;
	if (FileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)		Data.Flags |= FF_HIDDEN;
	if (FileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)		Data.Flags |= FF_SYSTEM;
	if (FileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)	Data.Flags |= FF_READONLY;
	if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)		Data.Flags |= FF_ARCHIVE;

	if (GetCRC)
	{
		FILE*	pFile = fopen(File.c_str(), "rb");
		if (pFile)
		{
			fseek(pFile, 0, SEEK_END);	// work out how big it is?
			unsigned int Size = ftell(pFile);
			fseek(pFile, 0, SEEK_SET);
			char*		BufferA = new char[Size];
			fread(BufferA, Size, 1, pFile);
			Data.Checksum = num::CRC32::Instance().crc32_calc(BufferA, Size);
			fclose(pFile);
			delete [] BufferA;
		}
	}
#endif //_PLATFORM_WIN32
	
	// TODO: Fix up non-win32 specific version of this?
	return true;
}

void						FileList::Build(const std::string& Path)
{

	bool		DidBuild = false;
	
	// retrieve the base-path, minus any wildcards
	std::string		BasePath, Ext;
	int				To = Path.find_last_of("/\\");
	if (To > 0)
	{
		BasePath = Path.substr(0, To+1);
	}
	To = Path.find_last_of(".");
	if (To > 0)
	{
		Ext = Path.substr(To+1, Path.size()-To);
		const char*	ExtStr = Ext.c_str();
	}

#ifdef _USEWIN32API_
	WIN32_FIND_DATA	FileData;
	HANDLE	hFile = FindFirstFile(Path.c_str(), & FileData);
	bool			FileOK = (hFile !=  INVALID_HANDLE_VALUE);

	while (FileOK)
	{
		FileInfo	NewFile;
		// file name
		NewFile.Name = BasePath + FileData.cFileName;
		if (!Ext.empty())
		{
		//	if (FileData.
		}
		

		// properties
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)	NewFile.Flags |= FF_DIRECTORY;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)		NewFile.Flags |= FF_HIDDEN;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)		NewFile.Flags |= FF_SYSTEM;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)	NewFile.Flags |= FF_READONLY;
		if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)		NewFile.Flags |= FF_ARCHIVE;

		//  write date
		SYSTEMTIME	time;
		FileTimeToSystemTime(&FileData.ftLastWriteTime, &time);
		char		Buffer[256];
		sprintf(Buffer, "%2i:%2i:%4i-%2i%2i", time.wDay, time.wMonth, time.wYear, time.wHour, time.wMinute);
		NewFile.Date = Buffer;

		m_Files.push_back(NewFile);

		FileOK = (FindNextFile(hFile, &FileData) != 0);
	}
	DidBuild = true;
	
#endif //_USEWIN32API_

#ifdef __APPLE__
	

		const char *path = BasePath.c_str();
		DIR* dir;		
		
		struct dirent *de;
		char* tmpS;
		_LOG(MSG_COMMENT, "Processing dir " << Path );
		dir = opendir(path);			
		
		if(dir)
		{
			tmpS =	(char*)malloc (1024);
			if (dir!=NULL) 
			{
				de=readdir(dir);
				while (de!=NULL)
				{ 
					if ((strcmp(de->d_name,".")!=0) && (strcmp(de->d_name,"..")!=0))  // CHIT: We don't process child dir
					{ 					
						FileInfo	NewFile;
						NewFile.Name = BasePath + de->d_name;
						m_Files.push_back(NewFile);
					} 
					de = readdir(dir);													// GOTO next
				}
			} 
			
			free(tmpS);
			closedir(dir);
			DidBuild = true;
		}
		else
		{
			_LOG( MSG_COMMENT,"could not read directory " << Path);
			return;
		}
		
#endif
	
#ifdef _PLATFORM_WII
		DVDDir	DirInfo;
		const char *path = BasePath.c_str();
	    if (FALSE == DVDOpenDir(path, &DirInfo))
	    {
			_LOG( MSG_COMMENT,"could not read directory " << Path);
	    	return;
	    }
		DVDDirEntry	DirEntry;
		while (DVDReadDir(&DirInfo, &DirEntry))
		{
			if (DirEntry.isDir) continue;	// not interested in sub-directories at the moment
			if (!Ext.empty())
			{
				if (strstr(DirEntry.name, Ext.c_str())==0) continue;	// not interesting 
			}


			FileInfo	NewFile;
			NewFile.Name = BasePath + DirEntry.name;
			m_Files.push_back(NewFile);
			
		}	
		DidBuild = true;
#endif //_PLATFORM_WII
	if(!DidBuild)
		_LOG( MSG_ERROR,"we didn't build files for " << Path);

}


void				FileList::Register(LuaPlus::LuaState* _pScript)
{
#ifdef _ENABLE_LUABIND
    lua_State * L = _pScript->GetCState ( );

	luabind::module(L)
    [ 
     luabind::class_<FileList>( "FileList" )
	 .def( "Build",				&FileList::Build)
	 .def( "GetFileC",			&FileList::GetFileC)
	 .def( "GetFileName",		&FileList::GetFileName)
	 .def( "GetBaseFileName",	&FileList::GetBaseFileName)
	 .def( "GetExtension",		&FileList::GetExtension)
	 .def( "IsDirectory",		&FileList::IsDirectory)
	 .def( "IsHidden",			&FileList::IsHidden)
	 .def( "IsSystem",			&FileList::IsSystem)
	 .def( "IsArchive",			&FileList::IsArchive)
	 .def( "IsReadOnly",		&FileList::IsReadOnly)
	 .def( "Reset",				&FileList::Reset)
	 .def( "SortNumber",		&FileList::SortNumber)
	 ];
	
    // Access file from config
	luabind::module(L) [ luabind::def( "SearchDir",			&SearchDir, luabind::detail::null_type() )  ];
	
#endif 
}

